#region Copyright

// The contents of this file are subject to the Mozilla Public License
//  Version 1.1 (the "License"); you may not use this file except in compliance
//  with the License. You may obtain a copy of the License at
//  
//  http://www.mozilla.org/MPL/
//  
//  Software distributed under the License is distributed on an "AS IS"
//  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//  License for the specific language governing rights and limitations under 
//  the License.
//  
//  The Initial Developer of the Original Code is Robert Smyth.
//  Portions created by Robert Smyth are Copyright (C) 2008.
//  
//  All Rights Reserved.

#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Alchemi.Core.Owner;
using NUnit.Extensions.NUnitGridRunner.Alchemi;
using NUnit.Extensions.NUnitGridRunner.Metrics;
using NUnit.Extensions.NUnitGridRunner.NUnit;
using NUnitGridThreadRunner.Alchemi;
using NUnitGridThreadRunner.Alchemi.Metrics;


namespace NUnit.Extensions.NUnitGridRunner
{
    public class Runner : IRunner
    {
        private readonly IApplicationSettings applicationSettings;
        private readonly ITextOutput textOutput;
        private TimeSpan expectedRunTime;
        private bool failed;
        private GThread[] gridRunners;

        public Runner(IApplicationSettings applicationSettings)
        {
            this.applicationSettings = applicationSettings;
            textOutput = new TextOutput(
                new TextOutputNode(true),
                new TextOutputNode(applicationSettings.Verbose),
                new TextOutputNode(!applicationSettings.NoWarn));
        }

        public void Run()
        {
            textOutput.Info.WriteLine("\nNUnitGridRunner Rev:{0}\n", Assembly.GetExecutingAssembly().GetName().Version);

            gridRunners = BuildGridThreads();

            if (!failed)
            {
                using (var gridApplication =
                    new GridApplication(applicationSettings.Server, applicationSettings.ServerPort,
                                        applicationSettings.WorkingDirectory, textOutput, expectedRunTime))
                {
                    failed = !gridApplication.Initialize();


                    if (!failed)
                    {
                        foreach (GThread thread in GridThreads)
                        {
                            gridApplication.AddThread(thread);
                        }

                        foreach (string assembly in applicationSettings.TestAssemblies)
                        {
                            string assemblyFullPath =
                                Path.Combine(applicationSettings.WorkingDirectory, assembly);
                            if (File.Exists(assemblyFullPath))
                            {
                                Assembly testAssembly = Assembly.LoadFrom(assemblyFullPath);
                                gridApplication.AddAssemblyToManifest(testAssembly);
                            }
                            else
                            {
                                textOutput.Info.WriteLine("Error: Unable to load assembly '{0}'.", assemblyFullPath);
                                failed = true;
                            }

                            if (!failed)
                            {
                                InitializeFoldersAndFiles();

                                if (!applicationSettings.Simulate)
                                {
                                    failed = !gridApplication.Run(applicationSettings.RunTimeLimit);
                                }

                                if (applicationSettings.NetworkTestMode)
                                {
                                    textOutput.Info.WriteLine();
                                    textOutput.Info.WriteLine("=============================");
                                    textOutput.Info.WriteLine("=  N E T W O R K   T E S T  =");
                                    textOutput.Info.WriteLine("=============================");
                                    textOutput.Info.WriteLine();
                                }
                                else if (applicationSettings.Simulate)
                                {
                                    textOutput.Info.WriteLine();
                                    textOutput.Info.WriteLine("=======================");
                                    textOutput.Info.WriteLine("=  S I M U L A T E D  =");
                                    textOutput.Info.WriteLine("=======================");
                                    textOutput.Info.WriteLine();
                                }
                            }
                        }
                    }
                }
            }
        }

        public bool Succeeded
        {
            get { return !failed; }
        }

        public GThread[] GridThreads
        {
            get { return gridRunners; }
        }

        private void InitializeFoldersAndFiles()
        {
            string testResultsLocalPath = GetLocalTestResultsPath();
            if (!Directory.Exists(testResultsLocalPath))
            {
                Directory.CreateDirectory(testResultsLocalPath);
            }
        }

        private string GetLocalTestResultsPath()
        {
            var environment = new TargetFileSystem(applicationSettings.WorkingDirectory);
            return environment.GetTestResultsFolder();
        }

        private GThread[] BuildGridThreads()
        {
            int newFixturesCount = 0;
            var gridThreads = new List<GThread>();

            Assembly nunitFramework = GetAssembly("nunit.framework.dll");
            Type testFixtureAttribute = nunitFramework.GetType("NUnit.Framework.TestFixtureAttribute");

            var testFixtues = new string[0];

            foreach (string assembly in applicationSettings.TestAssemblies)
            {
                string assemblyFullPath =
                    Path.Combine(applicationSettings.WorkingDirectory, assembly);
                if (File.Exists(assemblyFullPath))
                {
                    Assembly testAssembly = Assembly.LoadFrom(assemblyFullPath);

                    ISuiteMetrics suiteMetrics = GetSuiteMetrics();

                    var fixtureFinder = new FixtureFinder(testAssembly, testFixtureAttribute);
                    testFixtues = fixtureFinder.GetTestFixtures();

                    if (testFixtues.Length > 0)
                    {
                        var leveler =
                            new FixtureLoadLeveler(suiteMetrics, textOutput, testFixtues,
                                                   applicationSettings.RunnerTimeLoading);

                        IGridRunnerFactory runnerFactory = GetRunnerFactory();

                        string[][] executorFixtures = leveler.LeveledFixtures;
                        var threadGenerator =
                            new GridThreadGenerator(applicationSettings.WorkingDirectory,
                                                    Path.GetFileName(assemblyFullPath),
                                                    new TargetFileSystem(applicationSettings.WorkingDirectory).
                                                        GetTestResultsFolder(), suiteMetrics,
                                                    runnerFactory, applicationSettings.NetworkTestMode);
                        gridThreads.AddRange(threadGenerator.GetGridThreads(executorFixtures));

                        failed = !threadGenerator.Succeeded;

                        newFixturesCount = suiteMetrics.NewFixturesCount;
                    }
                    else
                    {
                        failed = true;
                        textOutput.Info.WriteLine("Error: No test fixtures found to run in assembly '{0}'.",
                                                  assemblyFullPath);
                    }
                }
                else
                {
                    textOutput.Info.WriteLine("Error: Unable to find test assembly {0}.", assemblyFullPath);
                    failed = true;
                }

                expectedRunTime = TimeSpan.Zero;
                foreach (GThread runner in gridThreads)
                {
                    if (runner is IGridRunner)
                    {
                        expectedRunTime += TimeSpan.FromSeconds(((IGridRunner) runner).ExpectedRunTimeInSeconds);
                    }
                }

                textOutput.Info.WriteLine("\nLoaded {0} fixtures into {1} grid runners.", testFixtues.Length,
                                          gridThreads.Count);
                if (newFixturesCount > 0)
                {
                    textOutput.Warning.WriteLine("Warning: Using default run time for {0} new fixtures.\n",
                                                 newFixturesCount);
                }
                textOutput.Info.WriteLine();
            }

            return gridThreads.ToArray();
        }

        private IGridRunnerFactory GetRunnerFactory()
        {
            string testResultsFolderPath =
                new TargetFileSystem(applicationSettings.WorkingDirectory).GetTestResultsFolder();

            return new AlchemiProcessGridRunnerFactory(applicationSettings.WorkingDirectory,
                                                       applicationSettings.RunnerTimeLimit,
                                                       applicationSettings.IgnorePriorFailures,
                                                       testResultsFolderPath);
        }

        private ISuiteMetrics GetSuiteMetrics()
        {
            ISuiteMetrics suiteMetrics;

            suiteMetrics =
                new SuiteMetrics(new FixtureMetricsCollection(new IFixtureMetrics[0]), TimeSpan.Zero,
                                 GetLocalTestResultsPath());

            return suiteMetrics;
        }

        private Assembly GetAssembly(string assemblyName)
        {
            string path = Path.Combine(applicationSettings.WorkingDirectory, assemblyName);
            Assembly assembly = Assembly.LoadFrom(path);
            if (assembly == null)
            {
                textOutput.Info.WriteLine("Error: Unable to load '{0}'.", path);
            }
            return assembly;
        }
    }
}